const ROOT_STORAGE_KEY = 'localGce';
export const STORAGE_ERROR_MSG = '⚠️ 本地保存失败，请重试';
// 写入串行队列，避免并发 patch 覆盖最新状态（例如 settings.dockIds）
let __patchQueue = Promise.resolve();

function readLocal(key) {
  return new Promise((resolve) => {
    try {
      chrome.storage.local.get([key], (res) => {
        if (chrome.runtime.lastError) {
          resolve(undefined);
        } else {
          resolve(res[key]);
        }
      });
    } catch (err) {
      resolve(undefined);
    }
  });
}

function writeLocal(key, value) {
  return new Promise((resolve, reject) => {
    try {
      chrome.storage.local.set({ [key]: value }, () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve();
        }
      });
    } catch (err) {
      reject(err);
    }
  });
}

export async function getRoot() {
  const cur = await readLocal(ROOT_STORAGE_KEY);
  if (cur) return cur;
  try {
    const legacy = await new Promise((resolve) => {
      try {
        chrome.storage.local.get(['localWeTab'], (res) => {
          if (chrome.runtime.lastError) resolve(undefined);
          else resolve(res['localWeTab']);
        });
      } catch (err) {
        resolve(undefined);
      }
    });
    if (legacy) {
      await setRoot(legacy);
      try { chrome.storage.local.remove('localWeTab'); } catch (err) {}
      return legacy;
    }
  } catch (err) {}
  return undefined;
}

export async function setRoot(data) {
  await writeLocal(ROOT_STORAGE_KEY, data);
}

export async function patchRoot(patch) {
  // 将读取-合并-写入串行化，保证每次合并都基于最新根数据
  __patchQueue = __patchQueue.then(async () => {
    const cur = await getRoot();
    if (!cur) {
      throw new Error('storage_unavailable');
    }
    const merged = mergeRoot(cur, patch || {});
    await setRoot(merged);
  });
  return __patchQueue;
}

/**
 * 根数据合并器（唯一入口）
 * 规范：
 * - settings 采用浅合并；engines/bookmarks/groups 采用“补丁优先，否则沿用当前”的策略
 * - dockIds 合并遵循：
 *   1) 以 dockStamp 为优先级（时间戳新者覆盖旧者）
 *   2) 5 秒内的“从非空到清空”将被视为误清空，除非 patch 携带 __dock_allow_empty === true
 *   3) 过滤非法 ID：仅保留存在于 bookmarks 集合中的 ID，防止外部错误写入污染
 * - 串行队列在 patchRoot() 中保证，无需再次加锁
 * 注意：本函数是全局唯一的合并点，任何模块如需修改 root，必须通过 patchRoot() 进入
 */
function mergeRoot(cur, patch){
  // 对 settings 做深合并，其他部分采用补丁优先，否则沿用当前的策略
  const curS = cur?.settings || {};
  const patchS = patch?.settings || {};
  const nextSettings = { ...curS, ...patchS };
  const allowEmpty = !!patch.__dock_allow_empty;
  // dockIds 使用时间戳优先：谁的 dockStamp 更新，就采用谁的 dockIds
  try{
    const cStamp = Number(curS?.dockStamp || 0);
    const pStamp = Number(patchS?.dockStamp || 0);
    const cDock = Array.isArray(curS?.dockIds) ? curS.dockIds : [];
    const pDock = Array.isArray(patchS?.dockIds) ? patchS.dockIds : (( 'dockIds' in patchS) ? [] : undefined);

    // 计算“合法书签ID集合”：来自当前与补丁的 bookmarks（避免外部将 icon-* 等写入 dockIds）
    const curBms = Array.isArray(cur?.bookmarks) ? cur.bookmarks : [];
    const patchBms = Array.isArray(patch?.bookmarks) ? patch.bookmarks : [];
    const validSet = new Set([
      ...curBms.map(b => b && b.id).filter(Boolean),
      ...patchBms.map(b => b && b.id).filter(Boolean),
    ]);
    const sanitize = (arr) => Array.isArray(arr) ? arr.filter(id => typeof id === 'string' && validSet.has(id)) : [];
    const cDockClean = sanitize(cDock);
    if (pDock !== undefined) {
      const pDockClean = sanitize(pDock);
      if (pStamp >= cStamp) {
        // 防呆：若补丁在 5 秒内尝试将非空列表清空，则忽略该次清空覆盖（除非明确允许）
        const tryingEmpty = Array.isArray(pDockClean) && pDockClean.length === 0;
        const curNonEmpty = Array.isArray(cDockClean) && cDockClean.length > 0;
        const tooClose = (pStamp - cStamp) >= 0 && (pStamp - cStamp) < 5000;
        if (tryingEmpty && curNonEmpty && tooClose && !allowEmpty) {
          nextSettings.dockIds = cDockClean;
          nextSettings.dockStamp = cStamp;
        } else {
          // 使用清洗后的补丁值（即使补丁提供了非法ID也会被剔除）
          nextSettings.dockIds = Array.isArray(pDockClean) ? pDockClean : [];
          nextSettings.dockStamp = pStamp || Date.now();
        }
      } else {
        nextSettings.dockIds = cDockClean;
        nextSettings.dockStamp = cStamp;
      }
    } else {
      // 补丁未显式携带 dockIds，则维持当前，并保留时间戳较大者
      nextSettings.dockIds = cDockClean;
      nextSettings.dockStamp = Math.max(cStamp, pStamp);
    }
  }catch{}
  return {
    settings: nextSettings,
    engines: (patch && 'engines' in patch) ? (patch.engines || []) : (cur.engines || []),
    bookmarks: (patch && 'bookmarks' in patch) ? (patch.bookmarks || []) : (cur.bookmarks || []),
    groups: (patch && 'groups' in patch) ? (patch.groups || []) : (cur.groups || []),
  };
}

export function makeInitialData() {
  const initSettings = {
    version: '1.0.0',
    openMode: 'newtab',
    iconSize: 'md',
    iconPx: 34,
    showName: true,
    fabEnabled: true,
    dockIds: [],
    dockStamp: 0,
    defaultSearchId: 'baidu',
    theme: 'dark',
    columns: 10,
    radius: 12,
    contentMaxWidth: 1400,
  };
  return {
    settings: initSettings,
    engines: [
      { id: 'baidu', name: '百度', template: 'https://www.baidu.com/s?wd=%s', builtin: true },
      { id: 'google', name: 'Google', template: 'https://www.google.com/search?q=%s', builtin: true },
      { id: 'duck', name: 'DuckDuckGo', template: 'https://duckduckgo.com/?q=%s', builtin: true },
      { id: 'yandex', name: 'Yandex', template: 'https://yandex.com/search/?text=%s', builtin: true },
    ],
    bookmarks: [],
    groups: [
      { id: 'default', name: '未分组', order: 10 },
    ],
  };
}

export { ROOT_STORAGE_KEY };
